package logic

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"laundry-service/entity"
	"math/rand"
	"net/http"
	"strings"
	"time"

	"github.com/shopspring/decimal"
	"github.com/wechatpay-apiv3/wechatpay-go/core"

	"laundry-service/app/client/internal/config"
	"laundry-service/app/client/internal/repo"
	"laundry-service/app/client/internal/svc"
	"laundry-service/app/client/internal/types"

	"github.com/zeromicro/go-zero/core/logx"
)

type WxRefundLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

type WYRefundReqBody struct {
	XMLName xml.Name `xml:"requestXml"`
	Head    string   `xml:"head"`
	Type    string   `xml:"type"`
	Code    string   `xml:"code"`
}

type WYRefundRespBody struct {
	Head     string `xml:"head"`
	ErrorMsg string `xml:"error_msg"`
}

func NewWxRefundLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxRefundLogic {
	return &WxRefundLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func GenerateNumber(pre string) string {
	date := time.Now().Format("060102150405")
	r := rand.Intn(10000)
	code := fmt.Sprintf("%s%s%04d", pre, date, r)
	return code
}

func (l *WxRefundLogic) GetRefundableOrderById(oid int) (*entity.Order, error) {
	o, err := l.svcCtx.Db.GetOrderById(oid)
	if err != nil {
		return nil, errors.New("订单不存在")
	}

	if time.Now().Unix()-o.CreatedAt.Unix() > 3600*24*30 {
		return nil, errors.New("超过一个月的订单不可退款")
	}

	if o.State == -1 {
		return nil, errors.New("该订单已取消")
	} else if o.State == 0 {
		return nil, errors.New("该订单暂未支付，不可退款")
	} else if o.State == -3 {
		return nil, errors.New("您已退款")
	} else if o.State != 1 || o.PayState != 1 {
		return nil, errors.New("该订单不可退款")
	}
	return o, nil
}

func CreateRefundTokenByOrder(DB repo.IUserDao, o *entity.Order) (refund *entity.OrderRefund, err error) {
	refund = new(entity.OrderRefund)
	refund.OrderNo = o.Number
	refund.RefundNo = GenerateNumber("r")
	refund.CreatedAt = time.Now()
	refund.Amount = o.RealAmount
	refund.Status = "PROCESSING"
	err = DB.OrderRefundSave(refund)
	if err != nil {
		return nil, err
	}
	return refund, nil
}

func (l *WxRefundLogic) ExcRefundByToken(token *entity.OrderRefund, order *entity.Order) (err error) {
	log := new(entity.OrderLog)
	log.OperatorId = order.UserId
	log.OperatorName = ""
	log.OrderId = order.ID
	log.OperatorType = 1
	log.State = -1
	log.CreatedAt = time.Now()
	log.Remark = "退款中"
	order.State = -1

	if token.Amount == 0 {
		log.Remark = "退款成功"
		token.SuccessTime = time.Now()
		return l.svcCtx.Db.TxRefund(order, token, log)
	}

	if order.PayState == 1 && order.ExpressBillCode != "" {
		// 已支付且成功下单 发起发起五优退款
		resp, err := WYRefundRequest(l.svcCtx.Config, order)
		if err != nil {
			log.State = -1
			log.Remark = "五优取消订单失败"
			log.Attr = fmt.Sprintf("%v", err)
			return l.svcCtx.Db.TxRefund(order, token, log)
		} else {
			token.Status = "ORIGINAL"
			log.Remark = "五优取消订单成功"
			log.Attr = resp
			err := l.svcCtx.Db.SaveOrderLog(log)
			if err != nil {
				logx.Error(err)
			}
		}
	}


	wxRefundLog := entity.NewStateLogWithOrder(order, -1, "微信退款中", "")

	responseBin, err := WxRefoundRequest(l.ctx, *l.svcCtx, order.Number, token.RefundNo, order.RealAmount)
	if err != nil {
		token.Status = "ABNORMAL"
		order.State = -1

		log1 := new(entity.OrderLog)
		log1.OrderId = order.ID
		log1.State = -3
		log1.Remark = "微信退款失败"
		log1.Attr = fmt.Sprintf("%v", err)
		return l.svcCtx.Db.TxRefund(order, token, log1)
	}

	token.Status = "PROCESSING"
	wxRefundLog.Remark = "申请微信退款"
	wxRefundLog.Attr = string(responseBin)
	_ = FinishRefundByWxResponse(*l.svcCtx, responseBin)
	return l.svcCtx.Db.TxRefund(order, token, wxRefundLog)
}

func FinishRefundByWxResponse(svcCtx svc.ServiceContext, responseBin []byte) error {
	var paymap map[string]string
	_ = json.Unmarshal(responseBin, &paymap)

	token, err := svcCtx.Db.GetOrderRefundByNo(paymap["out_refund_no"])
	if err != nil {
		return err
	}
	token.TransactionId = paymap["transaction_id"]
	token.RefundId = paymap["refund_id"]
	token.Channel = paymap["channel"]
	token.UserReceivedAccount = paymap["user_received_account"]
	a, _ := time.Parse("2006-01-02T15:04:05+08:00", paymap["create_time"])
	token.SuccessTime = a
	_ = svcCtx.Db.OrderRefundSave(token)
	return nil
}

func WxRefoundRequest(ctx context.Context, svcCtx svc.ServiceContext, tradeNo string, refundNo string, amount float64) (responseBin []byte, err error) {
	a := decimal.NewFromFloat(amount)
	b := decimal.NewFromInt(100)
	result := a.Mul(b).IntPart()

	mapInfo := map[string]interface{}{
		"out_trade_no":  tradeNo,
		"out_refund_no": refundNo,
		"notify_url":    svcCtx.Config.WX.GoodsRefundDNotifyUrl,
		"amount": map[string]interface{}{
			"refund":   result,
			"total":    result,
			"currency": "CNY",
		},
	}

	pri := fmt.Sprintf("退款请求参数: %v", mapInfo)
	fmt.Println(pri)

	URL := "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"
	wx, err := core.NewClient(ctx, svcCtx.Opt...)
	if err != nil {
		return nil, err
	}
	resp, err := wx.Post(ctx, URL, mapInfo)
	if err != nil {
		return nil, err
	}
	err = core.CheckResponse(resp.Response)
	if err != nil {
		return nil, err
	}
	body, err := io.ReadAll(resp.Response.Body)
	if err != nil {
		return nil, errors.New(err.Error())
	}
	return body, nil
}

func (l *WxRefundLogic) refundBusiness(req *types.WxRefundReq) (resp *types.Reply, err error) {

	o, e := l.GetRefundableOrderById(req.OrderId)
	if e != nil {
		return nil, e
	}
	
	t, e := CreateRefundTokenByOrder(l.svcCtx.Db, o)
	if e != nil {
		return nil, e
	}

	err = l.ExcRefundByToken(t, o)
	if err != nil {
		return nil, err
	}

	if o.UserCouponId != 0 {
	
		err = l.svcCtx.Db.UpdateUserCouponState(o.UserCouponId, 0)
		if err != nil {
			logx.Errorf("退还优惠券失败: %v", err)
			return nil, err
		}
	}
	resp = &types.Reply{}
	return resp, nil
}

func (l *WxRefundLogic) WxRefund(req *types.WxRefundReq) (resp *types.Reply, err error) {
	if req.OrderId == 0 {
		return
	} else {
		return l.refundBusiness(req)
	}
}

func WYRefundRequest(config config.Config, order *entity.Order) (string, error) {
	url := fmt.Sprintf("%s%s", config.WY.BaseURL, "/shopping/cancelOrder")

	reqData := WYRefundReqBody{
		Head: config.WY.Header,
		Type: "2",
		Code: order.ExpressOrderCode,
	}
	xmlData, err := xml.Marshal(reqData)
	if err != nil {
		return "", err
	}
	requestBody := bytes.NewBuffer([]byte("aesEncode=0&requestXml="))
	requestBody.Write(xmlData)
	req, err := http.NewRequest("POST", url, requestBody)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("failed to send request: %v", err)
	}

	strRespBody := string(body)
	if strings.HasPrefix(strRespBody, "<Response><head>OK") {
		return strRespBody, nil
	} else {
		return strRespBody, fmt.Errorf("refund wuyou failed: %s", strRespBody)
	}
}
